package pedro.encoder.input.gl.render.filters;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.view.Surface;
import android.view.View;

import androidx.annotation.RequiresApi;


import com.iraytek.rtsplib.R;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import pedro.encoder.utils.gl.GlUtil;
import pedro.encoder.utils.gl.TranslateTo;

/**
 * Created by pedro on 4/02/18.
 */

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class AndroidViewFilterRender extends BaseFilterRender {

    //rotation matrix
    private final float[] squareVertexDataFilter = {
            // X, Y, Z, U, V
            -1f, -1f, 0f, 0f, 0f, //bottom left
            1f, -1f, 0f, 1f, 0f, //bottom right
            -1f, 1f, 0f, 0f, 1f, //top left
            1f, 1f, 0f, 1f, 1f, //top right
    };

    private int program = -1;
    private int aPositionHandle = -1;
    private int aTextureHandle = -1;
    private int uMVPMatrixHandle = -1;
    private int uSTMatrixHandle = -1;
    private int uSamplerHandle = -1;
    private int uSamplerViewHandle = -1;

    private int[] viewId = new int[1];
    private View view;
    private SurfaceTexture surfaceTexture;
    private Surface surface;
    private final Handler mainHandler;

    private int rotation;
    private float positionX = 0, positionY = 0;
    private float scaleX = 1f, scaleY = 1f;
    private boolean loaded = false;
    private float viewX, viewY;
    private volatile boolean renderingView = false;

    public AndroidViewFilterRender() {
        squareVertex = ByteBuffer.allocateDirect(squareVertexDataFilter.length * FLOAT_SIZE_BYTES)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        squareVertex.put(squareVertexDataFilter).position(0);
        Matrix.setIdentityM(MVPMatrix, 0);
        Matrix.setIdentityM(STMatrix, 0);
        mainHandler = new Handler(Looper.getMainLooper());
    }

    @Override
    protected void initGlFilter(Context context) {
        String vertexShader = GlUtil.getStringFromRaw(context, R.raw.simple_vertex);
        String fragmentShader = GlUtil.getStringFromRaw(context, R.raw.android_view_fragment);

        program = GlUtil.createProgram(vertexShader, fragmentShader);
        aPositionHandle = GLES20.glGetAttribLocation(program, "aPosition");
        aTextureHandle = GLES20.glGetAttribLocation(program, "aTextureCoord");
        uMVPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
        uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix");
        uSamplerHandle = GLES20.glGetUniformLocation(program, "uSampler");
        uSamplerViewHandle = GLES20.glGetUniformLocation(program, "uSamplerView");

        GlUtil.createExternalTextures(1, viewId, 0);
        surfaceTexture = new SurfaceTexture(viewId[0]);
        surface = new Surface(surfaceTexture);
    }

    @Override
    protected void drawFilter() {
        surfaceTexture.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
        if (view != null && !renderingView) {
            renderingView = true;
            final Canvas canvas = surface.lockCanvas(null);
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            float scaleFactorX = 100f * (float) view.getWidth() / (float) getPreviewWidth();
            float scaleFactorY = 100f * (float) view.getHeight() / (float) getPreviewHeight();
            canvas.translate(positionX, positionY);
            canvas.rotate(rotation, viewX / 2f, viewY / 2f);
            if (!loaded) {
                scaleX = scaleFactorX;
                scaleY = scaleFactorY;
                loaded = true;
            }
            canvas.scale(scaleX / scaleFactorX, scaleY / scaleFactorY);
            try {
                view.draw(canvas);
                surface.unlockCanvasAndPost(canvas);
                renderingView = false;
                //Sometimes draw could crash if you don't use main thread. Ensuring you can render always
            } catch (Exception e) {
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        view.draw(canvas);
                        surface.unlockCanvasAndPost(canvas);
                        renderingView = false;
                    }
                });
            }
        }
        surfaceTexture.updateTexImage();

        GLES20.glUseProgram(program);

        squareVertex.position(SQUARE_VERTEX_DATA_POS_OFFSET);
        GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
                SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
        GLES20.glEnableVertexAttribArray(aPositionHandle);

        squareVertex.position(SQUARE_VERTEX_DATA_UV_OFFSET);
        GLES20.glVertexAttribPointer(aTextureHandle, 2, GLES20.GL_FLOAT, false,
                SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
        GLES20.glEnableVertexAttribArray(aTextureHandle);

        GLES20.glUniformMatrix4fv(uMVPMatrixHandle, 1, false, MVPMatrix, 0);
        GLES20.glUniformMatrix4fv(uSTMatrixHandle, 1, false, STMatrix, 0);

        GLES20.glUniform1i(uSamplerHandle, 4);
        GLES20.glActiveTexture(GLES20.GL_TEXTURE4);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, previousTexId);
        //android view
        GLES20.glUniform1i(uSamplerViewHandle, 5);
        GLES20.glActiveTexture(GLES20.GL_TEXTURE5);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, viewId[0]);
    }

    @Override
    public void release() {
        GLES20.glDeleteProgram(program);
    }

    public View getView() {
        return view;
    }

    public void setView(final View view) {
        this.view = view;
        if (view != null) {
            view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
            viewX = view.getMeasuredWidth();
            viewY = view.getMeasuredHeight();
        }
    }

    /**
     * @param x Position in percent
     * @param y Position in percent
     */
    public void setPosition(float x, float y) {
        int previewX = getPreviewWidth();
        int previewY = getPreviewHeight();
        this.positionX = previewX * x / 100f;
        this.positionY = previewY * y / 100f;
    }

    public void setPosition(TranslateTo positionTo) {
        int previewX = getPreviewWidth();
        int previewY = getPreviewHeight();
        switch (positionTo) {
            case TOP:
                this.positionX = previewX / 2f - (viewX / 2f);
                this.positionY = 0f;
                break;
            case LEFT:
                this.positionX = 0;
                this.positionY = previewY / 2f - (viewY / 2f);
                break;
            case RIGHT:
                this.positionX = previewX - viewX;
                this.positionY = previewY / 2f - (viewY / 2f);
                break;
            case BOTTOM:
                this.positionX = previewX / 2f - (viewX / 2f);
                this.positionY = previewY - viewY;
                break;
            case CENTER:
                this.positionX = previewX / 2f - (viewX / 2f);
                this.positionY = previewY / 2f - (viewY / 2f);
                break;
            case TOP_RIGHT:
                this.positionX = previewX - viewX;
                this.positionY = 0;
                break;
            case BOTTOM_LEFT:
                this.positionX = 0;
                this.positionY = previewY - viewY;
                break;
            case BOTTOM_RIGHT:
                this.positionX = previewX - viewX;
                this.positionY = previewY - viewY;
                break;
            case TOP_LEFT:
            default:
                this.positionX = 0;
                this.positionY = 0;
                break;
        }
    }

    public void setRotation(int rotation) {
        if (rotation < 0) {
            this.rotation = 0;
        } else if (rotation > 360) {
            this.rotation = 360;
        } else {
            this.rotation = rotation;
        }
    }

    public void setScale(float scaleX, float scaleY) {
        this.scaleX = scaleX;
        this.scaleY = scaleY;
        loaded = true;
    }

    public PointF getScale() {
        return new PointF(scaleX, scaleY);
    }

    public PointF getPosition() {
        int previewX = getPreviewWidth();
        int previewY = getPreviewHeight();
        return new PointF(positionX * 100 / previewX, positionY * 100 / previewY);
    }
}
